////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNMemory/inc/WNMemory.h"
#include "WNMath/inc/WNBox.h"
#include "WNMath/inc/WNBasic.h"
#include "WNMath/inc/WNBounds3.h"
#include "WNMath/inc/WNBox.h"
#include "WNMath/inc/WNExtended.h"
#include "WNMath/inc/WNMatrix33.h"
#include "WNMath/inc/WNSphere.h"
#include "WNMath/inc/WNVector3.h"

using namespace WNMath;
using namespace WNMemory;

// Constructors ////////////////////////////////////////////////////////////////////////////////////

WNBox::WNBox() :
    mLocation(0.0f, 0.0f, 0.0f),
    mExtents(0.0f, 0.0f, 0.0f),
    mRotation(0.0f, 0.0f, 0.0f, 1.0f) {
}

WNBox::WNBox(const WN_FLOAT32* _f) :
    mLocation(_f[0], _f[1], _f[2]),
    mExtents(_f[3], _f[4], _f[5]),
    mRotation(_f[6], _f[7], _f[8], _f[9]) {
}

WNBox::WNBox(const WNPoint3& _location, const WNVector3& _extents, const WNQuaternion& _rotation) :
    mLocation(_location),
    mExtents(_extents),
    mRotation(_rotation) {
}

WNBox::WNBox(const WNPoint3& _point) :
    mLocation(_point),
    mExtents(0.0f, 0.0f, 0.0f),
    mRotation(0.0f, 0.0f, 0.0f, 1.0f) {
}

// Operators ///////////////////////////////////////////////////////////////////////////////////////

WN_BOOL WNBox::operator == (const WNBox& _box) const {
    return(0 == WNMemCmp(this, &_box, sizeof(WNBox)));
}

WN_BOOL WNBox::operator != (const WNBox& _box) const {
    return(0 != WNMemCmp(this, &_box, sizeof(WNBox)));
}

// Members /////////////////////////////////////////////////////////////////////////////////////////

WN_VOID WNBox::Zero() {
    mLocation.Zero();
    mExtents.Zero();
    mRotation.Identity();
}

WN_VOID WNBox::Set(const WN_FLOAT32* _f) {
    mLocation.x = _f[0];
    mLocation.y = _f[1];
    mLocation.z = _f[2];

    mExtents.x = _f[3];
    mExtents.y = _f[4];
    mExtents.z = _f[5];

    mRotation.x = _f[6];
    mRotation.y = _f[7];
    mRotation.z = _f[8];
    mRotation.w = _f[9];
}

WN_VOID WNBox::Set(const WNPoint3& _location, WNVector3& _extents, const WNQuaternion& _rotation) {
    mLocation = _location;
    mExtents = _extents;
    mRotation = _rotation;
}

WN_VOID WNBox::Set(const WNPoint3& _point) {
    mLocation = _point;
    mExtents.Zero();
    mRotation.Identity();
}

WN_VOID WNBox::Expand(WN_FLOAT32 _amount) {
    mExtents += _amount;
}

WN_BOOL WNBox::Expand(const WNPoint3& _point, WN_BOOL _anchor) {
    const WNPoint3 transformedPoint = ((_point - mLocation).GetRotated(mRotation.GetInverted()).ToPoint3());

    if (!_anchor) {
        WNBounds3 bounds = WNBounds3(-mExtents.ToPoint3(), mExtents.ToPoint3());

        if (bounds.Expand(transformedPoint)) {
            const WNBox newRectange = bounds.ToBox();

            mLocation += newRectange.mLocation.ToVector3();
            mExtents = newRectange.mExtents;

            return(WN_TRUE);
        }
    } else {
        const WN_FLOAT32 transformedAbsX = WNAbs(transformedPoint.x);
        const WN_FLOAT32 transformedAbsY = WNAbs(transformedPoint.y);
        const WN_FLOAT32 transformedAbsZ = WNAbs(transformedPoint.z);
        const WN_FLOAT32 extendsAbsX = WNAbs(mExtents.x);
        const WN_FLOAT32 extendsAbsY = WNAbs(mExtents.y);
        const WN_FLOAT32 extendsAbsZ = WNAbs(mExtents.z);

        WN_BOOL expanded = WN_FALSE;

        if (transformedAbsX > extendsAbsX) {
            mExtents.x = transformedPoint.x;

            expanded = WN_TRUE;
        }

        if (transformedAbsY > extendsAbsY) {
            mExtents.y = transformedPoint.y;

            expanded = WN_TRUE;
        }

        if (transformedAbsZ > extendsAbsY) {
            mExtents.z = transformedPoint.z;

            expanded = WN_TRUE;
        }

        return(expanded);
    }

    return(WN_FALSE);
}

WN_BOOL WNBox::Expand(const WNBounds3& _bounds, WN_BOOL _anchor) {
    WNPoint3 points[8];

    _bounds.GetPoints(points);

    return(Expand(points[0], _anchor) || Expand(points[1], _anchor) ||
           Expand(points[2], _anchor) || Expand(points[3], _anchor) ||
           Expand(points[4], _anchor) || Expand(points[5], _anchor) ||
           Expand(points[6], _anchor) || Expand(points[7], _anchor));
}

WN_BOOL WNBox::Expand(const WNBox& _box, WN_BOOL _anchor) {
    WNPoint3 points[8];

    _box.GetPoints(points);

    return(Expand(points[0], _anchor) || Expand(points[1], _anchor) ||
           Expand(points[2], _anchor) || Expand(points[3], _anchor) ||
           Expand(points[4], _anchor) || Expand(points[5], _anchor) ||
           Expand(points[6], _anchor) || Expand(points[7], _anchor));
}

WN_BOOL WNBox::Expand(const WNSphere& _sphere, WN_BOOL _anchor) {
    const WNPoint3 transformedLocation = ((_sphere.mLocation - mLocation).GetRotated(mRotation.GetInverted()).ToPoint3());
    const WNVector3 componentX = WNVector3(_sphere.mRadius, 0.0f, 0.0f);
    const WNVector3 componentY = WNVector3(0.0f, _sphere.mRadius, 0.0f);
    const WNVector3 componentZ = WNVector3(0.0f, 0.0f, _sphere.mRadius);
    const WNPoint3 point1 = transformedLocation + componentX;
    const WNPoint3 point2 = transformedLocation - componentX;
    const WNPoint3 point3 = transformedLocation + componentY;
    const WNPoint3 point4 = transformedLocation - componentY;
    const WNPoint3 point5 = transformedLocation + componentZ;
    const WNPoint3 point6 = transformedLocation - componentZ;

    return(Expand(point1, _anchor) || Expand(point2, _anchor) ||
           Expand(point3, _anchor) || Expand(point4, _anchor) ||
           Expand(point5, _anchor) || Expand(point6, _anchor));
}

WN_VOID WNBox::Translate(WN_FLOAT32 _x, WN_FLOAT32 _y, WN_FLOAT32 _z) {
    mLocation.x += _x;
    mLocation.y += _y;
    mLocation.z += _z;
}

WN_VOID WNBox::Translate(const WNVector3& _vector) {
    mLocation += _vector;
}

WN_VOID WNBox::Rotate(const WNQuaternion& _rotation) {
    mRotation.Rotate(_rotation);
}

WN_VOID WNBox::Scale(WN_FLOAT32 _scale) {
    mExtents.Scale(_scale);
}

WN_VOID WNBox::Scale(WN_FLOAT32 _x, WN_FLOAT32 _y, WN_FLOAT32 _z) {
    mExtents.Scale(_x, _y, _z);
}

WN_VOID WNBox::Scale(const WNVector3& _scale) {
    mExtents.Scale(_scale);
}

WN_FLOAT32 WNBox::Length() const {
    return(WNAbs(2.0f * mExtents.x));
}

WN_FLOAT32 WNBox::Width() const {
    return(WNAbs(2.0f * mExtents.z));
}

WN_FLOAT32 WNBox::Height() const {
    return(WNAbs(2.0f * mExtents.y));
}

WN_FLOAT32 WNBox::SurfaceArea() const {
    const WN_FLOAT32 x = WNAbs(mExtents.x * 2.0f);
    const WN_FLOAT32 y = WNAbs(mExtents.y * 2.0f);
    const WN_FLOAT32 z = WNAbs(mExtents.z * 2.0f);

    const WN_FLOAT32 xy = x * y;
    const WN_FLOAT32 xz = x * z;
    const WN_FLOAT32 yz = y * z;

    return(xy + xz + yz);
}

WN_FLOAT32 WNBox::Volume() const {
    const WNVector3 dimensions = mExtents * 2.0f;

    return(dimensions.x * dimensions.y * dimensions.z);
}

WN_BOOL WNBox::IsZero() const {
    return(mLocation.IsZero() && mExtents.IsZero() && mRotation.IsZero());
}

WN_BOOL WNBox::IsInsideOut() const {
    return(mExtents.x < 0.0f || mExtents.y < 0.0f || mExtents.z < 0.0f);
}

WNBox WNBox::GetExpanded(WN_FLOAT32 _amount) const {
    WNBox box = *this;

    return(box.Expand(_amount), box);
}

WNBox WNBox::GetExpanded(const WNPoint3& _point, WN_BOOL _anchor) const {
    WNBox box = *this;

    return(box.Expand(_point, _anchor), box);
}

WNBox WNBox::GetExpanded(const WNSphere& _sphere, WN_BOOL _anchor) const {
    WNBox box = *this;

    return(box.Expand(_sphere, _anchor), box);
}

WNBox WNBox::GetExpanded(const WNBounds3& _bounds, WN_BOOL _anchor) const {
    WNBox box = *this;

    return(box.Expand(_bounds, _anchor), box);
}

WNBox WNBox::GetExpanded(const WNBox& _box, WN_BOOL _anchor) const {
    WNBox box = *this;

    return(box.Expand(_box, _anchor), box);
}

WNBox WNBox::GetTranslated(WN_FLOAT32 _x, WN_FLOAT32 _y, WN_FLOAT32 _z) {
    WNBox box = *this;

    return(box.Translate(_x, _y, _z), box);
}

WNBox WNBox::GetTranslated(const WNVector3& _vector) const {
    WNBox box = *this;

    return(box.Translate(_vector), box);
}

WNBox WNBox::GetRotated(const WNQuaternion& _rotation) const {
    WNBox box = *this;

    return(box.Rotate(_rotation), box);
}

WNBox WNBox::GetScaled(WN_FLOAT32 _scale) const {
    WNBox box = *this;

    return(box.Scale(_scale), box);
}

WNBox WNBox::GetScaled(WN_FLOAT32 _x, WN_FLOAT32 _y, WN_FLOAT32 _z) const {
    WNBox box = *this;

    return(box.Scale(_x, _y, _z), box);
}

WNBox WNBox::GetScaled(const WNVector3& _scale) const {
    WNBox box = *this;

    return(box.Scale(_scale), box);
}

WN_VOID WNBox::GetPoints(WNPoint3* _points) const{
    const WNVector3 transformedExtents = mExtents.GetRotated(mRotation);
    const WNVector3 transformedXExtents = (mExtents * WNVector3(-1.0f, 1.0f, 1.0f)).GetRotated(mRotation);
    const WNVector3 transformedYExtents = (mExtents * WNVector3(1.0f, -1.0f, 1.0f)).GetRotated(mRotation);
    const WNVector3 transformedZExtents = (mExtents * WNVector3(1.0f, 1.0f, -1.0f)).GetRotated(mRotation);

    _points[0] = mLocation - transformedExtents;
    _points[6] = mLocation + transformedExtents;
    _points[3] = mLocation - transformedXExtents;
    _points[5] = mLocation + transformedXExtents;
    _points[1] = mLocation - transformedYExtents;
    _points[7] = mLocation + transformedYExtents;
    _points[4] = mLocation - transformedZExtents;
    _points[2] = mLocation + transformedZExtents;
}